/*
 * @(#)Dom4jXmlDataHandler.java $version 2012-3-2
 * 
 * Copyright 2011 RogueDevelopment Gruop. All rights Reserved.
 * ROGUEDEVELOPMENT PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nadostar.extensions.data;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Branch;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.xml.sax.XMLReader;

import com.nadostar.extensions.util.FileResource;
import com.nadostar.extensions.util.Resource;
import com.nadostar.extensions.util.ResourceLoader;

/**
 * @author LEESUNG
 * 
 */
public class Dom4jXmlDataHandler extends AbstractDataHandler {

	/**
	 * 최상위 {@link Element}
	 */
	private Node element;

	/**
	 * 설정된 위치의 파일을 이용하여 XMLDataHandler를 생성한다.
	 */
	Dom4jXmlDataHandler(String location) {
		super(location);
		initialize();
	}

	/**
	 * {@link Element} 파라미터를 통해 XMLDataHandler를 생성한다.
	 */
	Dom4jXmlDataHandler(Node element) {
		this.element = element;
	}

	protected void initialize() {
		ResourceLoader loader = new ResourceLoader();
		Resource resource = loader.loadResource(location);

		if (resource == null) {
			throw new RuntimeException();
		}

		Element localElement = parseResource(resource);
		appendHandler(loader, localElement, localElement);

		if (resource instanceof FileResource) {
			((FileResource) resource).sync();
		}

		this.element = localElement;
		this.resource = resource;
	}

	public void destroy() {
	}

	/**
	 * 파일을 읽은 후 최상위 {@link Element}를 반환한다.
	 * 
	 * @location 환경 파일의 위치
	 * @loader 클래스 경로의 하위 자원을 검색하기 위한 {@link ClassLoader}
	 * @param resource
	 *            Resource
	 * @return Element
	 */
	private Element parseResource(Resource resource) {
		try {
			SAXReader saxReader = new SAXReader();
			setAllowJavaEncoding(saxReader.getXMLReader());
			InputStream inputStream = resource.openStream();
			try {
				return saxReader.read(inputStream).getRootElement();
			} finally {
				inputStream.close();
			}
		} catch (Exception e) {
			throw new DataHandlerException(e);
		}
	}

	/**
	 * xmlParser 에서 java-encodings 형식을 지원하도록 설정
	 * 
	 * @param xmlReader
	 *            XMLReader
	 */
	private void setAllowJavaEncoding(XMLReader xmlReader) {
		try {
			xmlReader
					.setFeature(
							"http://apache.org/xml/features/allow-java-encodings",
							true);
		} catch (Exception e) {
			log.info("failed to set feature( allow-java-encodings) caused of "
					+ e, e);
		} catch (AbstractMethodError error) {
			log.info("feature( allow-java-encodings) is not supported. use xerces 2.8 or above");
		}
	}

	/**
	 * 환경 정보의 include 속성으로 포함된 환경 정보를 읽어들여 최상위 노드에 추가한다.
	 * 
	 * @param loader
	 *            클래스 경로 하위 자원을 검색하기 위한 {@link ClassLoader}
	 * @param root
	 *            최상위 {@link Element}
	 * @param element
	 *            include 요소를 포함하고 있는 {@link Element}
	 */
	private void appendHandler(ResourceLoader loader, Element root,
			Element element) {
		List<Element> includes = element.elements("include");

		for (Element includeInfo : includes) {
			log.debug("appending resource " + includeInfo.getText() + " to "
					+ identity());
			Resource resource = loader.loadResource(includeInfo.getText());

			if (resource == null) {
				throw new RuntimeException();
			}

			Element included = parseResource(resource);

			List<Element> childNodes = included.elements();

			for (Element orgChildNode : childNodes) {
				root.add(orgChildNode.createCopy()); // appending to root..?
			}

			log.info(includeInfo.getText() + " is appended to " + identity());

			// recursively include sub-config
			appendHandler(loader, root, included);
		}
	}

	public String evaluate(String key, boolean resolve) {
		Node selected = element.selectSingleNode(key);

		if (selected != null) {
			String value = selected.getText();
			return resolve ? resolve(value) : value;
		}

		return null;
	}

	public boolean containsKey(String key) {
		return element.selectSingleNode(key) != null;
	}

	public List<String> getValues(String key) {
		List<Node> nodes = element.selectNodes(key);

		if (nodes == null) {
			return Collections.emptyList();
		}

		List<String> values = new ArrayList<String>(nodes.size());

		for (Node element : nodes) {
			values.add(element.getText());
		}

		return values;
	}

	public DataHandler peek(String path) {
		Node subConfigElement = element.selectSingleNode(path);

		if (subConfigElement == null) {
			return null;
		}

		return new Dom4jXmlDataHandler(subConfigElement);
	}

	public DataHandler select(String path) throws DataHandlerException {
		DataHandler handler = peek(path);

		if (handler == null) {
			throw new DataHandlerException(path + " is not found in "
					+ identity());
		}

		return handler;
	}

	public List<DataHandler> selectAll(String path) throws DataHandlerException {
		List<DataHandler> subConfigs = peekAll(path);

		if (subConfigs.isEmpty()) {
			throw new DataHandlerException(path + " is not found in "
					+ identity());
		}

		return subConfigs;
	}

	public List<DataHandler> peekAll(String path) {
		List<Node> nodeList = element.selectNodes(path);

		if (nodeList == null) {
			return Collections.emptyList();
		}

		List<DataHandler> subConfigs = new ArrayList<DataHandler>(
				nodeList.size());

		for (Node element : nodeList) {
			subConfigs.add(new Dom4jXmlDataHandler(element));
		}

		return subConfigs;
	}

	public List<DataHandler> peekChildren() {
		if (!(element instanceof Branch)) {
			return Collections.emptyList();
		}

		List<DataHandler> subConfigs = new ArrayList<DataHandler>(
				((Branch) element).nodeCount());
		Iterator<Node> nodes = ((Branch) element).nodeIterator();

		while (nodes.hasNext()) {
			Node childNode = nodes.next();

			if (childNode.getNodeType() == Node.ELEMENT_NODE) {
				subConfigs.add(new Dom4jXmlDataHandler(childNode));
			}
		}

		return subConfigs;
	}

	public String getRootName() {
		return element.getName();
	}
}
